Abel'Blog

我干了什么?究竟拿了时间换了什么?

0%

go[4]-测试

提高代码的质量,有两个路数:1.多测试;2.做代码review;如果能在代码放到外部去之前,做好单元测试,将会节省很多的时间(如果能把事情做好,减少很多处理bug的时间)。

单元测试(unit test)

单元测试能自动化,只是手段,而非目的。避免形式主义。需要持之以恒。想要习惯去做测试还是需要将测试的手段非常熟练,而且易用才能解开。

编写单元测试是对即将实现的算法做复核预演。

测试代码需要在当前包以_test.go结束的文件。

测试函数以Test为名字前缀。

测试命令go test忽略_,.开头的测试文件。

正常编译的时候,会忽略掉测试文件。

main_test.go

1
2
3
4
5
6
7
8
9
10
11
12
package main
import(
"testing"
)
func add(x,y int) int {
return x+y
}
func TestAdd(t *testing.T){
if add(1,2) != 3 {
t.FailNow()
}
}
1
2
3
4
5
6
$go test -v
[cq@localhost test_case]$ go test -v
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
PASS
ok test_case 0.002s

遍历子包测试用例。

testing.T提供的函数

方法 说明 相关
Fail 失败:继续执行当前测试用例
FailNow 失败:终止当前测试 Failed
SkipNow 跳过:停止执行当前测试函数 Skip,Skipf,Skipped
Log 输出日志,仅在失败或者-v时候输出 Logf
Parallel 与有相同设置的测试函数并行执行(一般会设置两个以上测试用例,否则就无用了)
Error Fail+Log Errorf
Fatal FailNow+Log Fatalf

测试命令常用的参数列表

参数 说明 实例
-args 命令行参数
-v 输出详细信息verbose
-parallel 并行执行多个测试用例
-run 运行指定测试用例,能支持正则表达式
-timeout 全部测试超时将会调用panic默认10ms -timeout 1m30s
-count 重复测试次数

table driven

使用table将测试参数都准备好,然后推进测试。测试数据和测试逻辑分离,有利于维护。

test main

如果定义了TestMain函数,go test将会执行这个函数,而不是每个具体的测试用例,这样就能把测试的setup/teardown机制用起来。比如测试一些db操作,需要先构造一个db的链接之类的,就可以使用这种模式。

1
2
3
func TestMain(m *testing.M) {
code :=m.Run()
os.Exit(code)

example

例代码是用于给GoDoc生成帮助文档。对比stdout结果和内部Output注释是否一致来判断是否成功

1
2
3
4
5
6
7
func ExampleAdd() {
fmt.Println(add(1,2))
fmt.Println(add(2,2))
// Output:
// 3
// 4
}

golang try catch 例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package main

import (
"fmt"
"github.com/sirupsen/logrus"
"runtime/debug"
)

func safeFunction() {
defer func() {
if r := recover(); r != nil {
logrus.Warnln("Recovered from panic:", r)
st := debug.Stack() // 打印调用堆栈信息
logrus.Warn("Stack trace:", string(st))
}
}()

// 模拟一个会导致数组越界的操作
arr := []int{1, 2, 3}
fmt.Println(arr[3]) // 这里会导致越界访问,触发 panic
}

func safeFunctionNilPoint() {
defer func() {
if r := recover(); r != nil {
logrus.Warnln("Recovered from panic:", r)
st := debug.Stack() // 打印调用堆栈信息
logrus.Warn("Stack trace:", string(st))
}
}()

// 模拟一个会导致空指针的错误
var ptr *int // 声明一个空指针

// 尝试对空指针进行解引用
*ptr = 10 // 这里会导致空指针错误
fmt.Println("This line will not be executed")
}

func main() {
fmt.Println("Starting program")

// 调用安全的函数
safeFunction()

safeFunctionNilPoint()

fmt.Println("Program continues to execute after panic")
}

性能测试

文件名也是使用的 _test.go结束的。函数名称需要使用Benchmark为前缀

1
2
3
4
5
6
7
8
9
func add(x,y int) int {
return x+y
}
func BenchmarkAdd(b *testing.B) {
for i :=0;i<b.N;i++ {
_ := add(1,2)
}
}
go test -bench

测试中使用断言

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestAdd(t *testing.T) {
assert.Equal(t, 2+2, 4)
}

希望忽略掉全部的单元测试,可以使用run=NONE。默认就是并发测试,可以通过-cpu参数设定多个并发限制。

使用 vscode 调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Attach to Process",
"type": "go",
"request": "attach",
"mode": "local",
"dlvFlags": [
"--check-go-version=false"
],
"processId": 0
},
{
"name": "debug client",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/client",
"dlvFlags": [
"--check-go-version=false"
],
"args": [
"--env",
"local"
]
},
{
"name": "debug walletserver",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}/main.go",
"args": []
}
]
}

组建测试

在 Go 语言中,并没有内置的 TestingSuite,但是您可以使用第三方库来实现测试套件的功能。一个常用的第三方库是 testify,它提供了丰富的断言和辅助函数,同时也支持测试套件的创建和执行。

要使用 testify 创建测试套件,您可以按照以下步骤进行操作:

首先,确保您已经安装了 testify 库。可以使用以下命令来安装它:

1
go get github.com/stretchr/testify

创建一个新的测试文件,并导入 testing 和 github.com/stretchr/testify/suite 包:

1
2
3
4
5
6
7
package mytests

import (
"testing"

"github.com/stretchr/testify/suite"
)

创建一个测试套件结构体,并嵌入 suite.Suite:

1
2
3
4
type MyTestSuite struct {
suite.Suite
// 添加自定义的测试套件字段或设置
}

在测试套件结构体中,可以定义 SetupSuite 和 TearDownSuite 方法,在整个测试套件运行前后进行设置和清理操作。也可以定义其他的测试辅助方法和测试函数。

创建测试函数,并使用 TestSuite 方法创建并运行测试套件:

1
2
3
func TestMySuite(t *testing.T) {
suite.Run(t, new(MyTestSuite))
}

在测试套件结构体中,可以定义测试函数,使用 suite 字段来调用断言和辅助函数。例如:

1
2
3
4
func (suite *MyTestSuite) TestSomething() {
// 使用断言和辅助函数进行测试
suite.Assert().Equal(2, 1+1)
}

这样,您就可以使用 testify 创建并执行测试套件了。执行测试时,可以使用 go test 命令来运行测试函数,或者使用集成开发环境(IDE)中的测试工具来运行测试套件。

请注意,以上步骤是基于 testify 库的示例,您也可以探索其他第三方库或编写自己的测试套件框架,以满足您的具体需求。